#!/usr/bin/env bash

include "objectv3.sh"
include "regex.sh"
include "test.sh"

objectv3::define_class Argumentsv2 "
    --integer=index
    --string=current
    --array=args
    --array=skipped
"

function Argumentsv2::Argumentsv2()
{
    local this="$1"; shift
    while (( $# > 0 )); do
        Argumentsv2::args "$this" push "$1"; shift
    done

    Argumentsv2::index "$this" -1
    if (( "$(Argumentsv2::index "$this")" + 1 < "$(Argumentsv2::args "$this" len)" )); then
        Argumentsv2::GoNext "$this"
    fi
}

function Argumentsv2::IsEnd()
{
    local this="$1"; shift
    (( "$(Argumentsv2::index "$this")" >= "$(Argumentsv2::args "$this" len)" ))
}

function Argumentsv2::GoNext()
{
    local this="$1"; shift
    if Argumentsv2::IsEnd "$this"; then
        assert_fail "没有更多参数，不能GoNext了"
        return 1
    fi

    Argumentsv2::index "$this" inc
    local index="$(Argumentsv2::index "$this")"
    Argumentsv2::current "$this" "$(Argumentsv2::args "$this" "$index")"
}

function Argumentsv2::GoSkip()
{
    local this="$1"; shift
    Argumentsv2::skipped "$this" push "$(Argumentsv2::Get "$this")"
    Argumentsv2::GoNext "$this"
}

function Argumentsv2::GoSkipAll()
{
    local this="$1"; shift
    while ! Argumentsv2::IsEnd "$this"; do
        Argumentsv2::GoSkip "$this"
    done
}

function Argumentsv2::Get()
{
    local this="$1"; shift
    Argumentsv2::current "$this"
}

function Argumentsv2::IsKVOption()
{
    local this="$1"; shift
    [[ "$(Argumentsv2::Get "$this")" =~ (^--${regex_id2}=) ]]
}

function Argumentsv2::IsOption()
{
    local this="$1"; shift
    [[ "$(Argumentsv2::Get "$this")" =~ (^-) ]]
}

function Argumentsv2::GetOption()
{
    local this="$1"; shift
    if ! Argumentsv2::IsOption "$this"; then
        assert_fail "GetOption需要当前值为选项，当前值: '$(Argumentsv2::Get "$this")'"
        return 1
    fi

    local option="$(Argumentsv2::Get "$this")"
    while [ "${option:0:1}" == "-" ]; do
        option="${option:1}"
    done
    echo "$option"
}

function Argumentsv2::GetKey()
{
    local this="$1"; shift
    if ! Argumentsv2::IsKVOption "$this"; then
        assert_fail "GetKey需要当前值为Key-Value选项，当前值: '$(Argumentsv2::Get "$this")'"
        return 1
    fi

    local option="$(Argumentsv2::Get "$this")"
    option="${option:2}"
    echo "${option%%=*}"
}

function Argumentsv2::GetVal()
{
    local this="$1"; shift
    if ! Argumentsv2::IsKVOption "$this"; then
        assert_fail "GetVal需要当前值为Key-Value选项，当前值: '$(Argumentsv2::Get "$this")'"
        return 1
    fi

    local option="$(Argumentsv2::Get "$this")"
    echo "${option#*=}"
}

function Argumentsv2::GetSkipped()
{
    local this="$1"; shift
    Argumentsv2::skipped "$this" all
}

function Argumentsv2::GetRest()
{
    local this="$1"; shift
    local i
    for (( i = $(Argumentsv2::index "$this"); i < $(Argumentsv2::args "$this" len); i++ )); do
        echo -n "$(Argumentsv2::args "$this" "$i") "
    done
}

function Argumentsv2::ProcessOption()
{
    local this="$1"; shift
    local null_args_mode="$1"; shift
    local not_option_mode="$1"; shift
    local option_func_prefix="$1"; shift

    if (( $(Argumentsv2::index "$this") > 0 )); then
        assert_fail "调用ProcessOption之前已被解析过了"
        return 1
    fi

    if Argumentsv2::IsEnd "$this"; then
        case "$null_args_mode" in
            null) ;;
            help) Argumentsv2::args "$this" push "--help"
                  Argumentsv2::GoNext "$this" ;;
            *) assert_fail "不认识的无参操作模式: $null_args_mode"
               return 1 ;;
        esac
    fi

    while ! Argumentsv2::IsEnd "$this"; do
        if ! Argumentsv2::IsOption; then
            case "$not_option_mode" in
                skip) Argumentsv2::GoSkip "$this"
                      continue ;;
                break) Argumentsv2::GoSkipAll "$this"
                       break ;;
                *) assert_fail "不认识的非选项操作模式: $not_option_mode"
                   return 1 ;;
            esac
        fi

        local option="$(Argumentsv2::Get "$this")"
        Argumentsv2::GoNext "$this"
        local func="${option_func_prefix}${option}"
        if ! declare -f $func 1>/dev/null; then
            loge "选项${option}的处理函数不存在"
        else
            $func
        fi
    done
}

function Argumentsv2::UseSkipped()
{
    local this="$1"; shift
    local skipped=($(Argumentsv2::GetSkipped "$this"))
    Argumentsv2::destroy arg "$this"
    Argumentsv2 "$this" "${skipped[@]}"
}

function Argumentsv2::Test1()
{
    Argumentsv2 arg -c --output a --index 2 --success --filter="123" normal

    # -c
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_true Argumentsv2::IsOption arg
    test::should_return_false Argumentsv2::IsKVOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be -c
    test::run_cmd Argumentsv2::GetOption arg
    test::should_be c
    Argumentsv2::GoNext arg

    # --output a
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_true Argumentsv2::IsOption arg
    test::should_return_false Argumentsv2::IsKVOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be --output
    test::run_cmd Argumentsv2::GetOption arg
    test::should_be output
    Argumentsv2::GoNext arg

    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_false Argumentsv2::IsOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be a
    Argumentsv2::GoNext arg

    # --index 2
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_true Argumentsv2::IsOption arg
    test::should_return_false Argumentsv2::IsKVOption arg
    Argumentsv2::GoSkip arg

    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_false Argumentsv2::IsOption arg
    Argumentsv2::GoSkip arg

    test::run_cmd Argumentsv2::GetSkipped arg
    test::should_be "--index 2"
    skipped=($(Argumentsv2::GetSkipped arg))
    test::run_cmd echo ${skipped[0]}
    test::should_be --index

    # --success
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_true Argumentsv2::IsOption arg
    test::should_return_false Argumentsv2::IsKVOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be --success
    test::run_cmd Argumentsv2::GetOption arg
    test::should_be success
    Argumentsv2::GoNext arg

    # --filter="123"
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_true Argumentsv2::IsOption arg
    test::should_return_true Argumentsv2::IsKVOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be --filter="123"
    test::run_cmd Argumentsv2::GetKey arg
    test::should_be filter
    test::run_cmd Argumentsv2::GetVal arg
    test::should_be 123
    Argumentsv2::GoNext arg

    # normal
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_false Argumentsv2::IsOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be normal
    Argumentsv2::GoNext arg

    test::should_return_true Argumentsv2::IsEnd arg
    Argumentsv2::destroy arg
}
test::register_test Argumentsv2::Test1

function Argumentsv2::Test2()
{
    Argumentsv2 arg -c 1

        Argumentsv2 arg2 -a 2

        # -a
        test::should_return_false Argumentsv2::IsEnd arg2
        test::should_return_true Argumentsv2::IsOption arg2
        test::run_cmd Argumentsv2::Get arg2
        test::should_be -a
        test::run_cmd Argumentsv2::GetOption arg2
        test::should_be a
        Argumentsv2::GoNext arg2

    # -c
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_true Argumentsv2::IsOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be -c
    test::run_cmd Argumentsv2::GetOption arg
    test::should_be c
    Argumentsv2::GoNext arg

        test::should_return_false Argumentsv2::IsEnd arg2
        test::should_return_false Argumentsv2::IsOption arg2
        test::run_cmd Argumentsv2::Get arg2
        test::should_be 2
        Argumentsv2::GoNext arg2

        test::should_return_true Argumentsv2::IsEnd arg2
        Argumentsv2::destroy arg2

    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_false Argumentsv2::IsOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be 1
    Argumentsv2::GoNext arg

    test::should_return_true Argumentsv2::IsEnd arg
    Argumentsv2::destroy arg
}
test::register_test Argumentsv2::Test2

function Argumentsv2::Test3()
{
    Argumentsv2 arg - --

    # -
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_true Argumentsv2::IsOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be -
    test::run_cmd Argumentsv2::GetOption arg
    test::should_be ""
    Argumentsv2::GoNext arg

    # -
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_true Argumentsv2::IsOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be --
    test::run_cmd Argumentsv2::GetOption arg
    test::should_be ""
    Argumentsv2::GoNext arg

    test::should_return_true Argumentsv2::IsEnd arg
    Argumentsv2::destroy arg
}
test::register_test Argumentsv2::Test3

function Argumentsv2::Test4()
{
    Argumentsv2 arg 1 2 3

    # 1
    test::should_return_false Argumentsv2::IsEnd arg
    test::should_return_false Argumentsv2::IsOption arg
    test::run_cmd Argumentsv2::Get arg
    test::should_be "1"
    Argumentsv2::GoNext arg

    test::run_cmd Argumentsv2::GetRest arg
    test::should_be '2 3 '
}
test::register_test Argumentsv2::Test4

function Argumentsv2::Test5()
{
    Argumentsv2 arg 1 2 3
    Argumentsv2::GoNext arg # 1
    Argumentsv2::GoSkip arg # 2
    Argumentsv2::GoSkip arg # 3
    Argumentsv2::UseSkipped arg

    # 2
    test::run_cmd Argumentsv2::Get arg
    test::should_be "2"
    Argumentsv2::GoNext arg

    # 3
    test::run_cmd Argumentsv2::Get arg
    test::should_be "3"
    Argumentsv2::GoNext arg
}
test::register_test Argumentsv2::Test5
